package calcsdk.nw;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class MethodPotential {

    BufferedImage img = null;
    JLabel imgLabel=null;
    JPanel imgPanel=null;
    private float[][] len = null;
    private float[] output=null;
    private float[] input=null;
    private float[][] mas=null;
    private float[] potH=null;
    private float[] potV=null;
    private float[][] pot=null;
    Table table=null;
    
    private boolean next_step=false;
    private boolean doNextStep() {
        if (next_step==true) {
            next_step=false;
            return true;
        } else
            return false;
    }
    private void waitForStep() {
        while (!doNextStep()) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException ex) {}
        }
    }
    
    
    private class Table {
        int numb_w, numb_h;
        int cell_w, cell_h;
        int w, h;
        int main_xshift, main_yshift;
        
        public Table(int cell_w, int cell_h) {
            updateParams(cell_w, cell_h);
        }
        
        public void updateParams(int cell_w, int cell_h) {
            this.cell_w=cell_w;
            this.cell_h=cell_h;
            numb_w = len[0].length;
            numb_h = len.length;
            w = cell_w * numb_w;
            h = cell_h * numb_h;
            main_xshift=cell_w;
            main_yshift=cell_h;
        }
        
        private void drawGrid(Graphics2D rc, int w,int h, int cell_w, int cell_h) {
            for (int i=0;i<=w;i+=cell_w)
                rc.drawLine(i, 0, i, h);
            for (int i=0;i<=h;i+=cell_h)
                rc.drawLine(0, i, w, i);
        }
        
        private void drawTextAlign(Graphics2D rc, int x, int y, String str, float h, float v) {
            FontMetrics fm = rc.getFontMetrics();
            int str_w=fm.stringWidth(str);
            int str_h=fm.getHeight();
            rc.drawString(str, x-str_w*h, y+str_h*(1-v));
        }
        
        private void draw(boolean fin) {
            Graphics2D rc = (Graphics2D) img.getGraphics();
            rc.setColor(Color.BLACK);
            rc.setFont(new Font("Monospaced",Font.BOLD,16));
            rc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            //FontMetrics fm = rc.getFontMetrics();
            //int str_h=fm.getHeight();
            
            rc.translate(main_xshift, main_yshift);
            AffineTransform transf=rc.getTransform();

            //draw input and potH row
            rc.translate(0, numb_h*cell_h);
            drawGrid(rc, w,cell_h*2, cell_w,cell_h);
            for (int i=0;i<numb_w;i++) {
                drawTextAlign(rc, i*cell_w+cell_w/2,cell_h/2, ""+input[i], 0.5f,0.5f);
                drawTextAlign(rc, i*cell_w+cell_w/2,cell_h+cell_h/2, ""+potH[i], 0.5f,0.5f);
            }
            rc.setTransform(transf);
            

            //draw output and potV column
            rc.translate(numb_w*cell_w, 0);
            drawGrid(rc, cell_w*2,h, cell_w,cell_h);
            for (int i=0;i<numb_h;i++) {
                drawTextAlign(rc, cell_w/2,i*cell_h+cell_h/2, ""+output[i], 0.5f,0.5f);
                drawTextAlign(rc, cell_w+cell_w/2,i*cell_h+cell_h/2, ""+potV[i], 0.5f,0.5f);
            }
            rc.setTransform(transf);
            

            //draw mas table
            drawGrid(rc, w,h, cell_w,cell_h);
            for (int i=0;i<numb_h;i++)
                for (int j=0;j<numb_w;j++) {
                    drawTextAlign(rc, j*cell_w+cell_w-2,i*cell_h, ""+(int)len[i][j], 1,0);
                    if (mas[i][j]==mas[i][j])
                        drawTextAlign(rc, j*cell_w+2,i*cell_h+cell_h-2, ""+(int)mas[i][j], 0,1);
                    if (pot[i][j]==pot[i][j])
                        drawTextAlign(rc, j*cell_w+2,i*cell_h, ""+(int)pot[i][j], 0,0);
                }
            rc.setTransform(transf);

            //транспортная работа
            rc.translate(0, cell_h*(numb_h+2));
            String workStr="Транспортная работа: "+getWork();
            drawTextAlign(rc, 0, 0, workStr, 0,0);
            if (fin) {
                FontMetrics fm = rc.getFontMetrics();
                int str_w=fm.stringWidth(workStr);
                int str_h=fm.getHeight();
                rc.setStroke(new BasicStroke(2));
                rc.drawRoundRect(-5, 0, str_w+10, str_h+5, 3, 3);
            }
            rc.setTransform(new AffineTransform());
            
            //imgLabel.repaint();
        }
        public void draw() {
            draw(false);
        }
        
        public void highlightCell(int x, int y) {
            Graphics2D rc = (Graphics2D) img.getGraphics();
            rc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            rc.translate(main_xshift+x*cell_w, main_yshift+y*cell_h);
            rc.setColor(Color.BLUE);
            int d=3;
            rc.setStroke(new BasicStroke(d));
            rc.drawRoundRect(0, 0, cell_w, cell_h, d*2, d*2);
            rc.setTransform(new AffineTransform());
            //imgLabel.repaint();
        }
        
        public void cellLine(int x1, int y1, int x2, int y2) {
            Graphics2D rc = (Graphics2D) img.getGraphics();
            rc.translate(main_xshift+cell_w/2, main_yshift+cell_h/2);
            rc.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
            rc.setColor(Color.RED);
            rc.setStroke(new BasicStroke(3));
            rc.drawLine(x1*cell_w, y1*cell_h, x2*cell_w, y2*cell_h);
            rc.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
            rc.setTransform(new AffineTransform());
        }
        public void cellLine(Point p0, Point p1) {
            cellLine(p0.x,p0.y,p1.x,p1.y);
        }
        public void drawTrace(LinkedList<Point> list) {
            Point cur,prev=list.getLast();
            for (Iterator<Point> it = list.iterator(); it.hasNext();) {
                cur = it.next();
                cellLine(cur,prev);
                prev=cur;
            }
        }
        
        public void clear() {
            Graphics2D rc = (Graphics2D) img.getGraphics();
            rc.setColor(Color.WHITE);
            rc.fillRect(0, 0, img.getWidth(), img.getHeight());
            rc.setColor(Color.BLACK);
        }
        
        public void refresh() {
            imgLabel.repaint();
        }
    }
    

    private void initGfx() {
        JFrame frame = new JFrame("pmf");
        JFrame.setDefaultLookAndFeelDecorated(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        img = new BufferedImage(800, 600, BufferedImage.TYPE_INT_ARGB);
        imgLabel = new JLabel(new ImageIcon(img));
        imgPanel = new JPanel();
        imgPanel.add(imgLabel);
        frame.getContentPane().add(imgPanel);
        
        JButton button = new JButton("Test button");
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                next_step=true;
            }
        });
        imgPanel.add(button);

        frame.pack();
        frame.setVisible(true);
    }

    private void fillRandom(int w, int h, int min, int max, int min_out, int max_out) {
        Random rnd = new Random();
        rnd.setSeed(5);
        len = new float[h][w];
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                len[i][j] = min + rnd.nextInt(max - min);
            }
        }
        
        output=new float[h];
        float summ=0;
        for (int i = 0; i < h; i++) {
            float r=min_out + rnd.nextInt(max_out - min_out);
            output[i]=r;
            summ+=r;
        }
        input=new float[w];
        for (int i = 0; i < w-1; i++) {
            int t=(int)summ/(w-i-1)-min_out;
            if (t<0) t=0;
            float r=min_out + rnd.nextInt(t);
            input[i]=r;
            summ-=r;
        }
        input[w-1]=summ;
    }
    
    private void fillByID(int id) {
        switch (id) {
        case 0:
            float tLen[][]={{12,6,9,4},{5,13,7,11},{2,16,10,8}};
            len=tLen;
            float tOut[]={300,500,700};
            output=tOut;
            float tIn[]={200,300,400,600};
            input=tIn;
            break;
        }
    }
    
    private void initArrays() {
        int w=len[0].length, h=len.length;
        mas=new float[h][w];
        potH=new float[w];
        potV=new float[h];
        pot=new float[h][w];
        for (int i=0;i<h;i++)
            for (int j=0;j<w;j++) {
                pot[i][j]=Float.NaN;
                mas[i][j]=Float.NaN;
            }
    }
    
  

    private float getWork() {
        float work=0;
        for (int i=0;i<mas.length;i++)
            for (int j=0;j<mas[0].length;j++)
                if (mas[i][j]==mas[i][j]) work+=mas[i][j]*len[i][j];
        return work;
    }
    
    private void fillMas() {
        int w=mas[0].length, h=mas.length;
        float outLeft[]=Arrays.copyOf(output, h);
        float inLeft[]=Arrays.copyOf(input, w);
        int outPos=0, inPos=0;
        
        int j=0;
        while (true) {
            float t=Math.min(outLeft[outPos], inLeft[inPos]);
            if (mas[outPos][inPos]==mas[outPos][inPos])
                mas[outPos][inPos]+=t;
            else
                mas[outPos][inPos]=t;
            outLeft[outPos]-=t;
            inLeft[inPos]-=t;
            if (outLeft[outPos]==0) {outPos++;}
            if (inLeft[inPos]==0) {inPos++;}
            if (inPos>=w || outPos>=h) break;
        }
    }
    
    private void fillPot() {
        int w=potH.length, h=potV.length;
        int i=0,j=0;
        Arrays.fill(potH,Float.NaN);
        Arrays.fill(potV,Float.NaN);
        for (int t=0;t<pot.length;t++) Arrays.fill(pot[t], Float.NaN);
        potV[0]=0;
//        while (true) {
//            while (j<w) {
//                table.clear();
//                table.draw();
//                table.highlightCell(j, i);
//                table.refresh();
//                waitForStep();
//                
//                if (mas[i][j]!=mas[i][j]) {j++; continue;}
//                
//                potH[j]=len[i][j]-potV[i];
//                
//                j++;
//            }
//            i++;
//            if (i==h) break;
//            j=0;
//            while (mas[i][j]!=mas[i][j]) {
//                table.clear();
//                table.draw();
//                table.highlightCell(j, i);
//                table.refresh();
//                waitForStep();
//                
//                j++;
//            }
//            potV[i]=len[i][j]-potH[j];
//        }
        
        boolean progress=true;
        while (progress) {
            progress=false;
            for (i=0;i<h;i++)
                for (j=0;j<w;j++) {
                    if (mas[i][j]!=mas[i][j]) continue;
                    if (potH[j]!=potH[j]) {potH[j]=len[i][j]-potV[i]; progress=true;} else
                        if (potV[i]!=potV[i]) {potV[i]=len[i][j]-potH[j]; progress=true;}
                }
        }
        
        for (i=0;i<h;i++)
            for (j=0;j<w;j++) {
                if (mas[i][j]!=mas[i][j]) pot[i][j]=potH[j]+potV[i];
            }
    }

//    private Point[] tracePoint2(Point p, LinkedList<Point> list) {
//        int w=potH.length, h=potV.length;
//        int min_i=-1,min_j=-1;
//        float min_val=Float.POSITIVE_INFINITY;
//        for (int j=0;j<w;j++)
//            if (mas[p.y][j]==mas[p.y][j] && mas[p.y][j]<min_val && j!=p.x) {
//                boolean ok=true;
//                for (Iterator<Point> it = list.iterator(); it.hasNext();) {
//                    Point cur = it.next();
//                    if (cur.x==j && cur.y==p.y) {ok=false; break;}
//                }
//                if (ok) {min_j=j; min_val=mas[p.y][j];}
//            }
//        
//        min_val=Float.POSITIVE_INFINITY;
//        for (int i=0;i<h;i++)
//            if (mas[i][p.x]==mas[i][p.x] && mas[i][p.x]<min_val && i!=p.y) {
//                boolean ok=true;
//                for (Iterator<Point> it = list.iterator(); it.hasNext();) {
//                    Point cur = it.next();
//                    if (cur.x==p.x && cur.y==i) {ok=false; break;}
//                }
//                if (ok) {min_i=i; min_val=mas[i][p.x];}
//            }
//        
//        Point r[]=new Point[2];
//        r[0]=min_i==-1 ? null : new Point(p.x,min_i);
//        r[1]=min_j==-1 ? null : new Point(min_j,p.y);
//        return r;
//    }
//    private Point tracePoint(Point p, LinkedList<Point> list) {
//        Point r[]=tracePoint2(p, list);
//		if (r[0]==null) {
//			if (r[1]==null) return null;
//			else return r[1];
//		}
//		if (r[1]==null) return r[0];
//        if (mas[r[0].y][r[0].x]<mas[r[1].y][r[1].x])
//            return r[0];
//        else
//            return r[1];
//    }
    private boolean tracePointH(Point start, Point cur, Point prev, LinkedList<Point> list) {
        int w=potH.length, h=potV.length;
        for (int j=0;j<w;j++) {
            if (mas[cur.y][j]==mas[cur.y][j] && j!=cur.x && (prev==null || j!=prev.x)) {
                Point t=new Point(j,cur.y);
                if (tracePointV(start, t, cur, list)) {
                    list.add(t);
                    //table.cellLine(cur, t);
                    //table.refresh();
                    //waitForStep();
                    return true;
                }
            } else {
                if (j==start.x && cur.y==start.y && prev!=null) {//в начале совпадают начальная и текущая точки, но предыдущей нет
                    list.add(start);
                    return true;
                }
            }
        }
        return false;
    }
    private boolean tracePointV(Point start, Point cur, Point prev, LinkedList<Point> list) {
        int w=potH.length, h=potV.length;
        for (int i=0;i<h;i++) {
            if (mas[i][cur.x]==mas[i][cur.x] && i!=cur.y && (prev==null || i!=prev.y)) {
                Point t=new Point(cur.x,i);
                if (tracePointH(start, t, cur, list)) {
                    list.add(t);
                    return true;
                }
            } else {
                if (cur.x==start.x && i==start.y) {
                    list.add(start);
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Поиск контура
     * @return true, если нужно продолжать, и наче - false
     */
    private boolean processPath() {
        int w=potH.length, h=potV.length;
        
        //find max pot diff
        Point max=new Point(0,0);
        float max_val=Float.NEGATIVE_INFINITY;
        for (int i=0;i<h;i++)
            for (int j=0;j<w;j++) {
                if (pot[i][j]==pot[i][j])
                    if (pot[i][j]-len[i][j]>=max_val && pot[i][j]>len[i][j]) {
                        max_val=pot[i][j]-len[i][j];
                        max.y=i;
                        max.x=j;
                    }
            }
        
        if (max_val==Float.NEGATIVE_INFINITY) return false;
        
        if (table!=null) table.highlightCell(max.x,max.y);
        
        table.refresh();
        waitForStep();
        
//        int min_i=0,min_j=0;
//        float min_val=Float.POSITIVE_INFINITY;
//        for (int j=0;j<w;j++)
//            if (mas[max.y][j]!=0 && mas[max.y][j]<min_val) {min_j=j; min_val=mas[max.y][j];}
//        min_val=Float.POSITIVE_INFINITY;
//        for (int i=0;i<h;i++)
//            if (mas[i][max.x]!=0 && mas[i][max.x]<min_val) {min_i=i; min_val=mas[i][max.x];}
//        
//        table.cellLine(max.x,max.y,max.x,min_i);
//        table.cellLine(max.x,max.y,min_j,max.y);
        
        //построение контура
        LinkedList<Point> pList=new LinkedList<Point>();
        tracePointH(max, max, null, pList);
        table.drawTrace(pList);
//        pList.add(max);
//        
//        Point r[]=tracePoint2(max, pList);
//        table.cellLine(max.x,max.y,r[0].x,r[0].y);
//        table.cellLine(max.x,max.y,r[1].x,r[1].y);
//        pList.addFirst(r[0]);
//        pList.addLast(r[1]);
//        
//        Point pr[]=new Point[2];
//        while (r[0].x!=r[1].x || r[0].y!=r[1].y) {
//            pr[0]=r[0];
//            pr[1]=r[1];
//            
//            r[0]=tracePoint(pr[0], pList);
//            table.cellLine(pr[0].x,pr[0].y,r[0].x,r[0].y);
//
//            r[1]=tracePoint(r[1], pList);
//            table.cellLine(pr[1].x,pr[1].y,r[1].x,r[1].y);
//            
//            pList.addFirst(r[0]);
//            pList.addLast(r[1]);
//            
//            table.refresh();
//            waitForStep();
//        }
        
        
        //нахождение минимального из углов с пометкой -
        //pList.removeFirst();//первый и последний - одинаковые
        float min_val=Float.POSITIVE_INFINITY;
        Point min=null;
        boolean isNeg=false;//((pList.size()-1)/2)%2==1 ? true : false;//первый элемент в списке с индексом + или -
        for (Iterator<Point> it = pList.iterator(); it.hasNext();) {
            Point cur = it.next();
            if (!isNeg) {isNeg=!isNeg; continue;}//перебор только отрицательных
            float m=mas[cur.y][cur.x];
            if (m==m && m<min_val) {
                min_val=m;
                min=cur;
            }
            isNeg=!isNeg;
        }
        if (table!=null) table.highlightCell(min.x,min.y);
        
        
        //потенциалы больше не нужны. всё равно пересчитывать
        Arrays.fill(potH,Float.NaN);
        Arrays.fill(potV,Float.NaN);
        for (int t=0;t<pot.length;t++) Arrays.fill(pot[t], Float.NaN);
        
        
        table.refresh();
        waitForStep();
        table.clear();
        
        
        //перераспределение
        for (Iterator<Point> it = pList.iterator(); it.hasNext();) {
            Point cur = it.next();
            if (mas[cur.y][cur.x]==mas[cur.y][cur.x]) {
                if (isNeg) {
                    if (mas[cur.y][cur.x]==min_val)
                         mas[cur.y][cur.x]=Float.NaN;
                    else mas[cur.y][cur.x]-=min_val;
                }
                else
                    mas[cur.y][cur.x]+=min_val;
            } else {
                mas[cur.y][cur.x]=min_val;
            }
            isNeg=!isNeg;
        }
        
        return true;
    }
    
    public MethodPotential() {
        initGfx();
        //fillRandom(4,3, 10,20, 10,20);
        fillByID(0);
        initArrays();
        
        table=new Table(64, 48);
        table.clear();
        
        fillMas();
        int c=0;
        while (true) {
            if (c==2) mas[0][0]=0;
            fillPot();

            table.clear();
            table.draw();

            if (!processPath()) break;

            table.draw();
            table.refresh();
            waitForStep();
            //table.clear();
            c++;
        }
        table.clear();
        table.draw(true);
        table.refresh();
    }
}
